import * as shlib from './index'
import type { Primitive, PlainArray, PlainObject, PlainType } from './types'


type Reactive<T extends PlainType> = T extends Primitive
    ? { value: T }
    : T

type ReactiveEventData<T extends PlainType> =
    T extends Primitive ? { value: T, oldValue: T} :
    T extends PlainArray ? { index: number, oldIndex?: number, value: T[number], oldValue?: T[number] } :
    T extends PlainObject ? { key: string | number | symbol, value: T[keyof T], oldValue?: T[keyof T] } :
    never



// 原始 PlainArray / PlainObject -> 它的响应式代理对象
const reactiveCache = new WeakMap<PlainArray | PlainObject, any>()

// 响应式代理对象 -> 按事件名归拢的监听器列表
const reactiveEventMap = new WeakMap<any, Record<string, ((event: string, data: any) => void)[]>>()

/**
 * 判断一个变量是否已经是响应式对象
 */
function isReactive(value: any): boolean {
    return reactiveEventMap.has(value)
}

/**
 * 定义 reactive 函数的界面，它既是一个函数，还带有三个静态方法
 */
const reactive: {
    <T extends PlainType>(value: T): Reactive<T>
    hook: <T extends PlainType>(proxy: Reactive<T>, event: string, callback: (event: string, data: ReactiveEventData<T>) => void) => void
    unhook: <T extends PlainType>(proxy: Reactive<T>, event: string, callback: (event: string, data: ReactiveEventData<T>) => void) => void
    emit: <T extends PlainType>(proxy: Reactive<T>, event: string, data: ReactiveEventData<T>) => void
} = Object.assign(
    /**
     * reactive 函数本体：将一个基本类型、简单数组或简单对象转化为响应式代理对象。
     */
    function<T extends PlainType>(value: T): Reactive<T> {
        // 如果已经是响应式代理对象，则直接返回
        if (isReactive(value))
            return value as Reactive<T>

        // 如果是基本类型，则直接构造新的响应式代理对象
        if (shlib.isPrimitive(value))
            return createPrimitiveProxy(value)

        // 如果不是基本类型，则先尝试从缓存中获取
        const proxy = reactiveCache.get(value as PlainArray | PlainObject)
        if (proxy)
            return proxy as Reactive<T>
        
        // 否则，创建简单数组的响应式代理对象
        if (shlib.isPlainArray(value))
            return createPlainArrayProxy(value)

        // 或者创建简单对象的响应式代理对象
        if (shlib.isPlainObject(value))
            return createPlainObjectProxy(value)

        throw new Error('argument is not a plain type')
    },
    
    /**
     * 静态方法，用于登记、撤销、触发数据变化事件。
     */
    {
        hook: function<T extends PlainType>(proxy: Reactive<T>, event: string, callback: (event: string, data: ReactiveEventData<T>) => void) {
            const eventMap = reactiveEventMap.get(proxy)
            if (!eventMap)
                return

            const callbacks = eventMap[event]
            if (!callbacks)
                eventMap[event] = [callback,]
            else
                callbacks.push(callback)
        },
        unhook: function<T extends PlainType>(proxy: Reactive<T>, event: string, callback: (event: string, data: ReactiveEventData<T>) => void) {
            const callbacks = reactiveEventMap.get(proxy)?.[event]
            if (!callbacks)
                return

            const index = callbacks.indexOf(callback)
            if (index !== -1)
                callbacks.splice(index, 1)
        },
        emit: function<T extends PlainType>(proxy: Reactive<T>, event: string, data: ReactiveEventData<T>) {
            const callbacks = reactiveEventMap.get(proxy)?.[event]
            if (!callbacks)
                return

            for (const cb of callbacks)
                cb(event, data)
        }
    }
)

function createPrimitiveProxy<T extends Primitive>(value: T): Reactive<T> {
    const target = { value: value }
    
    // 基本类型的响应式代理对象，具有 { value: T } 的界面
    const proxy = new Proxy(target, {
        // 不需要监听 get 操作，直接回退到 target.value

        // 拦截对 value 属性的 set 操作，触发对应的监听器
        set(target, key, value) {
            if (key === 'value') {
                const oldValue = target.value
                target.value = value
                reactive.emit<T>(proxy, 'update', { value, oldValue } as ReactiveEventData<T>)
            }
            return true
        }
    }) as unknown as Reactive<T>

    // 初始化事件监听器列表
    reactiveEventMap.set(proxy, { update: [] })

    return proxy
}

function createPlainArrayProxy<T extends PlainArray>(value: T): Reactive<T> {
    const target = value

    // 代理的数组方法
    const propValueMap: Record<string|symbol, any> = {
        push: (...items: (typeof target[number])[]) => {
            let startIndex = target.length
            target.push(...items)
            for (const it of items) {
                reactive.emit(proxy, 'insert', { index: startIndex, value: it } as ReactiveEventData<T>)
                startIndex++
            }
            return target.length
        },
        pop: (): typeof target[number] | undefined => {
            const oldValue = target.pop()
            if (oldValue !== undefined)
                reactive.emit(proxy, 'delete', { index: target.length, value: oldValue } as ReactiveEventData<T>)
            return oldValue
        }
        // ...
    }

    // 简单数组的响应式代理对象具有该数组本身的界面
    const proxy = new Proxy(target, {
        get(target, key, receiver) {
            // 拦截数组方法
            const value = propValueMap[key]
            if (value)
                return value

            // 处理数组下标索引
            const index = Number(key)
            if (typeof key === 'string' && !isNaN(index)) {
                const item = target[index]
                return shlib.isPrimitive(item)? item: reactive(item)
            }

            // length 等其他数组属性或方法回退给 target
            return Reflect.get(target, key, receiver)
        },
        set(target, key, value: PlainType) {
            const index = Number(key)
            if (typeof key === 'string' && !isNaN(index)) {
                const oldValue = target[index]
                target[index] = value
                if (index < target.length)
                    reactive.emit(proxy, 'update', { index, value, oldValue } as ReactiveEventData<T>)    
                else
                    reactive.emit(proxy, 'insert', { index, value } as ReactiveEventData<T>)
            }
            return true
        }
    }) as unknown as Reactive<T>
    
    // 初始化事件监听器列表，并缓存该响应式代理对象
    reactiveEventMap.set(proxy, { insert: [], update: [], delete: [] })
    reactiveCache.set(target, proxy)

    return proxy
}


function createPlainObjectProxy<T extends PlainObject>(value: T): Reactive<T> {
    const target = value
    
    // 简单对象的响应式代理对象具有该对象本身的界面
    const proxy = new Proxy(target, {
        get(target, key, receiver) {
            const value = Reflect.get(target, key, receiver)
            return shlib.isPrimitive(value)? value: reactive(value)
        },
        set(target, key, value: PlainType, receiver) {
            const oldValue = Reflect.get(target, key, receiver)
            // 下面这行用 Reflect.set() 而不是 target[key] = value 是为了绕过 tsc 类型推断报错
            Reflect.set(target, key, value, receiver)
            if (oldValue !== undefined)
                reactive.emit(proxy, 'update', { key, value, oldValue } as ReactiveEventData<T>)
            else
                reactive.emit(proxy, 'insert', { key, value } as ReactiveEventData<T>)
            return true
        }
    }) as unknown as Reactive<T>

    // 初始化事件监听器列表，并缓存该响应式代理对象
    reactiveEventMap.set(proxy, { update: [] })
    reactiveCache.set(target, proxy)
    
    return proxy
}

